<?php

namespace Core;

/**
 * Class Request
 * 封装 HTTP 请求信息，提供对 GET, POST, SERVER 等超全局变量的安全、只读访问。
 *
 * @package Core
 */
class Request
{
    /**
     * GET 请求参数 (来自 $_GET)
     * @var array
     */
    private array $get;

    /**
     * POST 请求参数 (来自 $_POST)
     * @var array
     */
    private array $post;

    /**
     * 服务器和执行环境信息 (来自 $_SERVER)
     * @var array
     */
    private array $server;

    /**
     * HTTP 请求方法 (e.g., 'GET', 'POST')
     * @var string
     */
    private string $method;

    /**
     * 请求的 URI 路径 (不包含查询字符串)
     * @var string
     */
    private string $uri;

    /**
     * 原始请求体 (raw body, e.g., for JSON/XML payloads)
     * @var string
     */
    private string $body;

    /**
     * HTTP Cookie (来自 $_COOKIE)
     * @var array
     */
    private array $cookies;

    /**
     * HTTP 请求头
     * @var array
     */
    private array $headers;

    /**
     * Request 构造函数.
     * 初始化所有请求属性，并确保在不同环境下（如 CLI 或 Web）的健壮性。
     */
    public function __construct()
    {
        // 1. 封装超全局变量
        $this->get     = $_GET;
        $this->post    = $_POST;
        $this->server  = $_SERVER;
        $this->cookies = $_COOKIE;

        // 2. 健壮性处理
        // 确保 REQUEST_METHOD 存在，为 CLI 或未知 SAPI 提供默认值
        $this->method = $this->server['REQUEST_METHOD'] ?? 'GET';

        // 确保 REQUEST_URI 存在，并处理 parse_url 可能失败的情况
        $rawUri = $this->server['REQUEST_URI'] ?? '/';
        $this->uri = parse_url($rawUri, PHP_URL_PATH) ?: '/';

        // 确保 file_get_contents('php://input') 失败时返回空字符串
        $this->body = file_get_contents('php://input') ?: '';

        // 3. 处理请求头 (兼容 Nginx/FPM)
        if (function_exists('getallheaders')) {
            $headers = getallheaders();
            $this->headers = ($headers === false) ? $this->parseHeadersFromServer() : $headers;
        } else {
            $this->headers = $this->parseHeadersFromServer();
        }
    }

    /**
     * 备用方案：当 getallheaders() 不可用时，从 $_SERVER 变量中解析请求头。
     *
     * @return array
     */
    private function parseHeadersFromServer(): array
    {
        $headers = [];
        foreach ($this->server as $key => $value) {
            // 匹配 'HTTP_' 开头的变量
            if (str_starts_with($key, 'HTTP_')) {
                // 将 HTTP_CONTENT_TYPE 转换为 Content-Type
                $headerKey = str_replace(
                    ' ',
                    '-',
                    ucwords(strtolower(str_replace('_', ' ', substr($key, 5))))
                );
                $headers[$headerKey] = $value;
            }
            // 处理没有 'HTTP_' 前缀的特殊头
            elseif (in_array($key, ['CONTENT_TYPE', 'CONTENT_LENGTH', 'CONTENT_MD5'], true)) {
                $headerKey = str_replace(
                    ' ',
                    '-',
                    ucwords(strtolower(str_replace('_', ' ', $key)))
                );
                $headers[$headerKey] = $value;
            }
        }
        return $headers;
    }

    // --- 公共 Getters (用于安全访问私有属性) ---

    /**
     * 获取 HTTP 请求方法
     *
     * @return string (e.g., 'GET', 'POST')
     */
    public function getMethod(): string
    {
        return $this->method;
    }

    /**
     * 获取请求的 URI 路径
     *
     * @return string (e.g., '/users/profile')
     */
    public function getUri(): string
    {
        return $this->uri;
    }

    /**
     * 获取原始请求体 (Raw Body)
     *
     * @return string
     */
    public function getBody(): string
    {
        return $this->body;
    }

    /**
     * 获取所有 HTTP 请求头
     *
     * @return array
     */
    public function getHeaders(): array
    {
        return $this->headers;
    }

    /**
     * 获取单个请求头 (大小写不敏感)
     *
     * @param string $key 头名称 (e.g., 'Content-Type')
     * @param mixed|null $default 默认值
     * @return string|mixed|null
     */
    public function getHeader(string $key, mixed $default = null): mixed
    {
        // 尝试通过 'Key' 格式查找
        $normalizedKey = str_replace(' ', '-', ucwords(str_replace('-', ' ', strtolower($key))));
        if (isset($this->headers[$normalizedKey])) {
            return $this->headers[$normalizedKey];
        }

        // 备用查找（getallheaders() 返回的键大小写可能不一致）
        $lowerKey = strtolower($key);
        foreach ($this->headers as $header => $value) {
            if (strtolower($header) === $lowerKey) {
                return $value;
            }
        }

        return $default;
    }

    /**
     * 获取所有 SERVER 数据
     *
     * @return array
     */
    public function getServer(): array
    {
        return $this->server;
    }

    /**
     * 获取所有 Cookie 数据
     *
     * @return array
     */
    public function getCookies(): array
    {
        return $this->cookies;
    }

    /**
     * 获取所有 GET (Query) 参数
     *
     * @return array
     */
    public function getQueryData(): array
    {
        return $this->get;
    }

    /**
     * 获取所有 POST 参数
     *
     * @return array
     */
    public function getPostData(): array
    {
        return $this->post;
    }

    // --- 实用方法 ---

    /**
     * 从请求(POST优先,然后GET)中获取一个输入值。
     *
     * **安全提示:** 此方法返回 *原始* 数据。
     * 如果用于HTML输出，请务必进行转义 (e.g., htmlspecialchars)。
     *
     * @param string $key 输入的键名
     * @param mixed $default (可选) 如果未找到，返回的默认值
     * @return mixed 返回找到的值或默认值
     */
    public function input(string $key, mixed $default = null): mixed
    {
        // 优先从 POST 中获取，然后从 GET 中获取
        return $this->post[$key] ?? $this->get[$key] ?? $default;
    }

    /**
     * 从 GET (query string) 中获取一个输入值。
     *
     * @param string $key 键名
     * @param mixed $default 默认值
     * @return mixed
     */
    public function get(string $key, mixed $default = null): mixed
    {
        return $this->get[$key] ?? $default;
    }

    /**
     * 从 POST 中获取一个输入值。
     *
     * @param string $key 键名
     * @param mixed $default 默认值
     * @return mixed
     */
    public function post(string $key, mixed $default = null): mixed
    {
        return $this->post[$key] ?? $default;
    }


    /**
     * 检查当前的请求方法是否为指定方法。
     *
     * @param string $method 要检查的方法名 (e.g., 'POST', 'post', 'GET')
     * @return bool
     */
    public function is(string $method): bool
    {
        // 统一转为大写进行比较，更健壮
        return strtoupper($this->method) === strtoupper($method);
    }
}